package com.yztstudy.service.impl;

import com.yztstudy.dto.Result;
import com.yztstudy.entity.SeckillVoucher;
import com.yztstudy.entity.VoucherOrder;
import com.yztstudy.mapper.VoucherOrderMapper;
import com.yztstudy.service.ISeckillVoucherService;
import com.yztstudy.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yztstudy.utils.RedisConstants;
import com.yztstudy.utils.RedisIdWorker;
import com.yztstudy.utils.UserHolder;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.*;

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;


    /**
     * TODO 抢购秒杀优惠券,防止超卖(乐观锁)、实现一人一单(分布式锁)
     * @param voucherId 秒杀优惠券的主键
     * @return
     */
    @Transactional
    @Override
    public Result seckillVoucher(Long voucherId) {

        // TODO 1.查询优惠券
        SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);

        // TODO 2.判断是否在时间范围内
        LocalDateTime beginTime = seckillVoucher.getBeginTime(); //开始时间
        LocalDateTime endTime = seckillVoucher.getEndTime(); //结束时间
        if (beginTime.isAfter(LocalDateTime.now()) || endTime.isBefore(LocalDateTime.now()))
        {
            return Result.fail("抢购秒杀券不在规定的时间范围以内!");
        }

        // TODO 3.判断库存是否充足
        int stock = seckillVoucher.getStock();
        if (stock < 1)
        {
            return Result.fail("秒杀券库存不足!");
        }

        // TODO 4.返回订单ID
        Long userId = UserHolder.getUser().getId();
        //4.1获取分布式锁
        RLock lock = redissonClient.getLock(RedisConstants.KEY_PREFIX + "order:" + userId);
        boolean isLock = false;
        try {
            isLock = lock.tryLock(1L, 10L, TimeUnit.SECONDS); //1秒钟内没有获取到锁就返回false，锁的失效时间为10秒钟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //4.2如果获取锁失败,即同一个用户进行了并发请求
        if (!isLock)
        {
            return Result.fail("一个人只允许下一单!");
        }
        //4.3获取锁成功
        try {
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy(); //获取代理对象
            return proxy.createVoucher(voucherId);
        }finally {
            // simpleRedisLock.unLock(); //释放锁

            lock.unlock(); //释放锁
        }
    }


    /**
     * TODO 创建优惠券订单
     * @param voucherId
     * @return
     */
    @Transactional
    public Result createVoucher(Long voucherId)
    {
        // TODO 1.一人一单，每一个秒杀券(voucher_id)只能被一个user_id购买一次
        Long userId = UserHolder.getUser().getId();
        long count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if (count > 0) //查询出来的行数大于0，就表示已经购买了一次!
        {
            return Result.fail("用户已经购买过了,不允许下单!");
        }

        // TODO 2.如果所有校验都通过了，就减少库存，使用乐观锁避免超卖问题
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId) // where voucher_id = xxx
                .gt("stock", 0) // and stock > 0
                .update();

        if (!success)
        {
            return Result.fail("秒杀券库存不足!");
        }

        // TODO 3.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();

        long orderID = redisIdWorker.nextId("order");//创建订单ID
        voucherOrder.setId(orderID);
        voucherOrder.setUserId(userId);
        voucherOrder.setVoucherId(voucherId);

        // TODO 4.订单写入数据库
        save(voucherOrder);

        return Result.ok(userId);
    }
}
